/**
 *  @file  vsTimeUtils.h version 1.0 2007-11-20 
 *  @brief Definition of class TimeUtility.
 *  Copyright (c) 2007 by @author <abitfree@gmail.com>
 */

#ifndef __VS_TIME_UTIL_H__
#define __VS_TIME_UTIL_H__

#include <string>
#include <memory>
#include "vsDefines.h"

namespace vs
{

#ifdef WIN32
typedef struct 
{
	time_t    tv_sec;     /* Seconds. */
	long      tv_usec;    /* Microseconds. */
} timeval;
#endif

/**
 @brief A group of helper functions for time processing.
 *
 * All the functions are declared as static public.
 */
class TimeUtility
{
private:
	/// Disable instancing.
	TimeUtility();

	/// Disable deleting.
	~TimeUtility();

public:
	/// Convert time to a string.
	static int    Format(char *out, size_t max_len, const char *format, const timeval *tp);

	/// Convert a YYYYMMDDhh24miss string to time.
	static time_t String2Time(const char *time14);

	/// Calculate the distance of two YYYYMMDDhh24miss string time.
	static time_t DiffTime(const char *time14start, const char *time14end);

	/// Convert the arguments to a time.
	static int    DIYTime2Time14(int year, int month, int day, int hour, int minute, int second, char *out, int max_length=0);

	/// Judging whether it is leap year or not.
	static int    IsLeapYear(int year);

	/// Get the last day of the month.
	static int    LastDayOfTheMonth(int year, int month);

	/// Wrapper for unix.
	static struct tm* localtime_r(const time_t *timer, struct tm* &result);

	/// Wrapper for unix.
	static struct tm* gmtime_r(const time_t *timer, struct tm* &result);
};


/**
 @brief Wrapper class for DateTime.
 */
class DateTime
{
public:
	/// Constructor, initialize to a given calendar time.
	DateTime(int year = 0, int month = 0, int day = 0, int hour = 0, int minute = 0, int second = 0);

	/// The copying constructor.
	DateTime(const DateTime &another);

	/// The destructor.
	~DateTime();

	/// Set to a given calendar time.
	void Set(int year, int month, int day, int hour, int minute, int second);

	//@{
	/// The setter methods.
	void SetYear(int year);
	void SetMonth(int month);
	void SetDay(int day);
	void SetHour(int hour);
	void SetMinute(int minute);
	void SetSecond(int second);
	//@}

	//@{
	/// The getter methods.
	int GetYear() const;
	int GetMonth() const;
	int GetDay() const;
	int GetHour() const;
	int GetMinute() const;
	int GetSecond() const;
	//@}

	/// Get the day of the year.
	int GetDayOfTheYear() const;

	/// Get the absolute time.
	long GetAbsoluteSecond() const;

	//@{
	/// For date time calculating.
	void AddYear(int y);
	void AddMonth(int m);
	void AddDay(int d);
	void AddHour(int h);
	void AddMinute(int m);
	void AddSecond(int s);
	//@}

	/// Update to the current system time.
	void Update();

	//@{
	/**
	 * @brief Format the TimeStamp object to a string.
	 *
	 * @param format a printf style formating string.
	 */
	int Format(char *out, int max_len, const char* format) const;
	int Format(const std::string &format, std::string &out) const;
	//@}

	/// The operator = function.
	DateTime& operator = (const DateTime& another);

	//@{
	/// The logical comparing functions.
	bool operator < (const DateTime& another) const;
	bool operator <= (const DateTime& another) const;
	bool operator > (const DateTime& another) const;
	bool operator >= (const DateTime& another) const;
	bool operator == (const DateTime& another) const;
	bool operator != (const DateTime& another) const;
	//@}

private:
	// Helper function, update to a special time.
	void Update(struct tm* special_time);

	// The broken time.
	struct tm broken_time_;
};


/**
 * @brief Wrapper class for timestamp.
 * TimeStamp is a certain time point of the system, it is often
 * used when a special event is happened.
 */
class TimeStamp
{
public:
	/**
	 * @brief Initialize from a given calendar time.
	 *
	 * @param second the second from 1970, if zero, it means using the current system time.
	 * @param microsecond the microsecond of this second, if zero, it means using the current system time.
	 * @sa Set()
	 */
	TimeStamp(long second = 0, long microsecond = 0);

	/// The copying constructor.
	TimeStamp(const TimeStamp &another);

	/// The destructor.
	~TimeStamp();

	/**
	 * @brief Set to a given calendar time.
	 * @param second the second from 1970, if zero, it means using the current system time.
	 * @param microsecond the microsecond of this second, if zero, it means using the current system time.
	 */
	void Set(long second, long microsecond);

	//@{
	/// The setter methods.
	void SetSecond(int second);
	void SetMicroSecond(long microsecond);
	//@}

	//@{
	/// The getter methods.
	long GetSecond() const;
	long GetMicroSecond() const;
	//@}

	/// Update to the current system time.
	void Update();

	//@{
	/**
	 * @brief Format the TimeStamp object to a string.
	 * @param format a printf style formating string.
	 */
	int Format(char *out, int max_len, const char* format) const;
	int Format(const std::string &format, std::string &out) const;
	//@}

	/// The operator = function.
	TimeStamp& operator = (const TimeStamp& another);

	//@{
	/// The logical comparing functions.
	bool operator < (const TimeStamp& another) const;
	bool operator <= (const TimeStamp& another) const;
	bool operator > (const TimeStamp& another) const;
	bool operator >= (const TimeStamp& another) const;
	bool operator == (const TimeStamp& another) const;
	bool operator != (const TimeStamp& another) const;
	//@}

private:
	// Update to a special time.
	void Update(struct tm* special_time);

	// The calendar time.
	timeval time_val_;
};

/////////////////////////////////////////////////////////////////////
// Member functions of TimeUtility.
/////////////////////////////////////////////////////////////////////

INLINE
time_t TimeUtility::DiffTime(const char *time14start, const char *time14end)
{
	return(String2Time(time14start) - String2Time(time14end));
}

INLINE
int TimeUtility::IsLeapYear(int year)
{
	return ((year % 400 == 0) || ((year % 4 == 0) && (year % 100 != 0)));
}

INLINE
int TimeUtility::LastDayOfTheMonth(int year, int month)
{
	static int day_of_month[][13] = {
		{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},	// for normal year
		{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},	// for leap year
	};
	if (IsLeapYear(year))
		return day_of_month[1][month];
	else
		return day_of_month[0][month];
}

INLINE
struct tm* TimeUtility::localtime_r(const time_t *timer, struct tm* &result)
{
#ifdef WIN32
	struct tm *ptm = localtime(timer);
	memcpy(result, ptm, sizeof(result));
	return result;
#else
	return localtime_r(timer, result);
#endif
}

INLINE
struct tm* TimeUtility::gmtime_r(const time_t *timer, struct tm* &result)
{
#ifdef WIN32
	struct tm *ptm = gmtime(timer);
	memcpy(result, ptm, sizeof(result));
	return result;
#else
	return gmtime_r(timer, result);
#endif
}


/////////////////////////////////////////////////////////////////////
// Member functions of TimeStamp.
/////////////////////////////////////////////////////////////////////

INLINE
DateTime::DateTime(int year, int month, int day, int hour, int minute, int second)
{
	Set(year, month, day, hour, minute, second);
}

INLINE
DateTime::DateTime(const DateTime &another)
{
	memcpy(&broken_time_, &another.broken_time_, sizeof(broken_time_));
}

INLINE
DateTime::~DateTime()
{
}

INLINE
void DateTime::SetYear(int year)
{
	broken_time_.tm_year = year - 1900;	// based on 1900
	Update(&broken_time_);
}

INLINE
void DateTime::SetMonth(int month)
{
	broken_time_.tm_mon = month - 1;	// based on 1
	Update(&broken_time_);
}

INLINE
void DateTime::SetDay(int day)
{
	broken_time_.tm_mday = day;
	Update(&broken_time_);
}

INLINE
void DateTime::SetHour(int hour)
{
	broken_time_.tm_hour = hour;
	Update(&broken_time_);
}

INLINE
void DateTime::SetMinute(int minute)
{
	broken_time_.tm_min = minute;
	Update(&broken_time_);
}

INLINE
void DateTime::SetSecond(int second)
{
	broken_time_.tm_sec = second;
	Update(&broken_time_);
}

INLINE
int DateTime::GetYear() const
{
	return broken_time_.tm_year + 1900;
}

INLINE
int DateTime::GetMonth() const
{
	return broken_time_.tm_mon + 1;
}

INLINE
int DateTime::GetDay() const
{
	return broken_time_.tm_mday;
}

INLINE
int DateTime::GetHour() const
{
	return broken_time_.tm_hour;
}

INLINE
int DateTime::GetMinute() const
{
	return broken_time_.tm_min;
}

INLINE
int DateTime::GetSecond() const
{
	return broken_time_.tm_sec;
}

INLINE
int DateTime::GetDayOfTheYear() const
{
	return broken_time_.tm_yday;
}

INLINE
long DateTime::GetAbsoluteSecond() const
{
	return mktime((tm*)&broken_time_);
}

INLINE
void DateTime::AddYear(int y)
{
	broken_time_.tm_year += y;
	Update(&broken_time_);
}

INLINE
void DateTime::AddMonth(int m)
{
	broken_time_.tm_mon += m;
	Update(&broken_time_);
}

INLINE
void DateTime::AddDay(int d)
{
	broken_time_.tm_mday += d;
	Update(&broken_time_);
}

INLINE
void DateTime::AddHour(int h)
{
	broken_time_.tm_hour += h;
	Update(&broken_time_);
}

INLINE
void DateTime::AddMinute(int m)
{
	broken_time_.tm_min += m;
	Update(&broken_time_);
}

INLINE
void DateTime::AddSecond(int s)
{
	broken_time_.tm_sec += s;
	Update(&broken_time_);
}

INLINE
void DateTime::Update()
{
	Set(0, 0, 0, 0, 0, 0);
}

INLINE
void DateTime::Update(struct tm* special_time)
{
	time_t second = mktime(special_time);

	// back to set special_time.tm_wday and special_time.tm_yday
	TimeUtility::localtime_r(&second, special_time);
}

INLINE
int DateTime::Format(char *out, int max_len, const char* format) const
{
	timeval tv;
	tv.tv_sec = GetAbsoluteSecond();
	tv.tv_usec = 0;
	return TimeUtility::Format(out, max_len, format, &tv);
}

INLINE
int DateTime::Format(const std::string &format, std::string &out) const
{
	char buf[256] = "";
	Format(buf, 256, format.c_str());
	out = buf;
	return out.size();
}

INLINE
DateTime& DateTime::operator = (const DateTime& another)
{
	if (&another != this)
	{
		memcpy(&broken_time_, &another.broken_time_, sizeof(broken_time_));
	}

	return *this;
}

INLINE
bool DateTime::operator < (const DateTime& another) const
{
	return this->GetAbsoluteSecond() < another.GetAbsoluteSecond();
}

INLINE
bool DateTime::operator > (const DateTime& another) const
{
	return this->GetAbsoluteSecond() > another.GetAbsoluteSecond();
}

INLINE
bool DateTime::operator == (const DateTime& another) const
{
	return this->GetAbsoluteSecond() == another.GetAbsoluteSecond();
}

INLINE
bool DateTime::operator <= (const DateTime& another) const
{
	return !(*this > another);
}

INLINE
bool DateTime::operator >= (const DateTime& another) const
{
	return !(*this < another);
}

INLINE
bool DateTime::operator != (const DateTime& another) const
{
	return !(*this == another);
}


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
INLINE
TimeStamp::TimeStamp(long second, long microsecond)
{
	Set(second, microsecond);
}

INLINE
TimeStamp::TimeStamp(const TimeStamp &another)
{
	memcpy(&time_val_, &another.time_val_, sizeof(time_val_));
}

INLINE
TimeStamp::~TimeStamp()
{
}

INLINE
void TimeStamp::Set(long second, long microsecond)
{
	if (second <= 0)		// second less or equal than 0, for current time
	{
#ifndef WIN32
		gettimeofday(&time_val_, 0);
#else
		time(&time_val_.tv_sec);
		time_val_.tv_usec = 0;
#endif // WIN32
	}
	else
	{
		time_val_.tv_sec = second;
		time_val_.tv_usec = ((microsecond < 0) ? 0 : microsecond);
	}
}

INLINE
void TimeStamp::SetSecond(int second)
{
	time_val_.tv_sec = second;
}

INLINE
void TimeStamp::SetMicroSecond(long microsecond)
{
	time_val_.tv_usec = microsecond;
}

INLINE
long TimeStamp::GetSecond() const
{
	return time_val_.tv_sec;
}

INLINE
long TimeStamp::GetMicroSecond() const
{
	return time_val_.tv_usec;
}

INLINE
void TimeStamp::Update()
{
	Set(0, 0);
}

INLINE
void TimeStamp::Update(struct tm* special_time)
{
	time_val_.tv_sec = mktime(special_time);
	time_val_.tv_usec = 0;
}

INLINE
int TimeStamp::Format(char *out, int max_len, const char* format) const
{
	return TimeUtility::Format(out, max_len, format, &time_val_);
}

INLINE
int TimeStamp::Format(const std::string &format, std::string &out) const
{
	char buf[256] = "";
	Format(buf, 256, format.c_str());
	out = buf;
	return out.size();
}

INLINE
TimeStamp& TimeStamp::operator = (const TimeStamp& another)
{
	if (&another != this)
	{
		memcpy(&time_val_ , &another.time_val_, sizeof(time_val_));
	}

	return *this;
}

INLINE
bool TimeStamp::operator < (const TimeStamp& another) const
{
	return ((time_val_.tv_sec < another.time_val_.tv_sec) ||
		((time_val_.tv_sec == another.time_val_.tv_sec) && (time_val_.tv_usec < another.time_val_.tv_usec)));
}

INLINE
bool TimeStamp::operator > (const TimeStamp& another) const
{
	return ((time_val_.tv_sec > another.time_val_.tv_sec) ||
		((time_val_.tv_sec == another.time_val_.tv_sec) && (time_val_.tv_usec > another.time_val_.tv_usec)));
}

INLINE
bool TimeStamp::operator == (const TimeStamp& another) const
{
	return ((time_val_.tv_sec == another.time_val_.tv_sec) && (time_val_.tv_usec == another.time_val_.tv_usec));
}

INLINE
bool TimeStamp::operator <= (const TimeStamp& another) const
{
	return !(*this > another);
}

INLINE
bool TimeStamp::operator >= (const TimeStamp& another) const
{
	return !(*this < another);
}

INLINE
bool TimeStamp::operator != (const TimeStamp& another) const
{
	return !(*this == another);
}

#define CHECK_OUT_OF_LENGTH(buf, current_length, step, max_length, out_of_length) \
	if (current_length+step >= max_length)\
{\
	*(buf+current_length) = '\0';\
	out_of_length = true;\
	break;\
}

INLINE
int TimeUtility::Format(char *out, size_t max_len, const char *format, const timeval *tp)
{
	if ((0 == out) || (max_len <= 0))
	{
		return 0;
	}

	char   *dst = out;
	struct tm *tm_time = localtime(&tp->tv_sec);
	char   fmt;
	size_t real_len = 0;
	int    step = 0;
	bool   out_of_length = false;

	while (((fmt = *format++) != '\0') && !out_of_length)
	{
		if (fmt == '%')
		{
			switch (fmt = *format++)
			{
			case '%':
				CHECK_OUT_OF_LENGTH(dst, real_len, 1, max_len, out_of_length);
				*dst++ = fmt;
				real_len += 1;
				break;
			case 'y':
				CHECK_OUT_OF_LENGTH(dst, real_len, 2, max_len, out_of_length);
				dst += sprintf(dst, "%02d", (tm_time->tm_year+1900)%100);
				real_len += 2;
				break;
			case 'Y':
				CHECK_OUT_OF_LENGTH(dst, real_len, 4, max_len, out_of_length);
				dst += sprintf(dst, "%04d", tm_time->tm_year+1900);
				real_len += 4;
				break;
			case 'm':
				CHECK_OUT_OF_LENGTH(dst, real_len, 2, max_len, out_of_length);
				dst += sprintf(dst, "%02d", tm_time->tm_mon+1);
				real_len += 2;
				break;
			case 'd':
				CHECK_OUT_OF_LENGTH(dst, real_len, 2, max_len, out_of_length);
				dst += sprintf(dst, "%02d", tm_time->tm_mday);
				real_len += 2;
				break;
			case 'H':
				CHECK_OUT_OF_LENGTH(dst, real_len, 2, max_len, out_of_length);
				dst += sprintf(dst, "%02d", tm_time->tm_hour);
				real_len += 2;
				break;
			case 'M':
				CHECK_OUT_OF_LENGTH(dst, real_len, 2, max_len, out_of_length);
				dst += sprintf(dst, "%02d", tm_time->tm_min);
				real_len += 2;
				break;
			case 'S':
				CHECK_OUT_OF_LENGTH(dst, real_len, 2, max_len, out_of_length);
				dst += sprintf(dst, "%02d", tm_time->tm_sec);
				real_len += 2;
				break;
			case 'U':
				CHECK_OUT_OF_LENGTH(dst, real_len, 6, max_len, out_of_length);
				dst += sprintf(dst, "%06d", tp->tv_usec);
				real_len += 6;
				break;
			default:
				perror("Time Format Error in TimeUtility::Format() :");
				return -1;
			}
		}
		else
		{
			*dst++ = fmt;
			++real_len;
		}
	}                                             // end of while

	if (*dst != '\0')
	{
		*dst = '\0';
	}

	return real_len;
}
#undef CHECK_OUT_OF_LENGTH

INLINE
time_t TimeUtility::String2Time(const char *time14)
{
	struct tm tm_time;
	char buf[15] = "";

	int length = strlen(time14);
	if (length < 14)
		return -1;

	/* Transfer to YYYYMMDDhh24miss */
	memset(&tm_time, 0, sizeof(tm_time));
	memcpy(buf, time14, 14);
	buf[14] = '\0';

	tm_time.tm_isdst = 0;

	tm_time.tm_sec = atoi(buf+12);
	buf[12] = '\0';

	tm_time.tm_min = atoi(buf+10);
	buf[10] = '\0';

	tm_time.tm_hour = atoi(buf+8);
	buf[8] = '\0';

	tm_time.tm_mday = atoi(buf+6);
	buf[6] = '\0';

	tm_time.tm_mon = atoi(buf+4) - 1;
	buf[4] = '\0';

	tm_time.tm_year = atoi(buf) - 1900;

	return(mktime(&tm_time));
}

INLINE
int TimeUtility::DIYTime2Time14(int year, int month, int day, int hour, int minute, int second, char *out, int max_length)
{
	struct tm stm;
	stm.tm_sec  = second;
	stm.tm_min  = minute;
	stm.tm_hour = hour;
	stm.tm_mday = day;
	stm.tm_mon  = month - 1;
	stm.tm_year = year - 1900;

	time_t now  = mktime(&stm);

	if (max_length == 0)
	{
		return sprintf(out, "%04d%02d%02d%02d%02d%02d", stm.tm_year+1900, stm.tm_mon+1, stm.tm_mday, stm.tm_hour, stm.tm_min, stm.tm_sec);
	}
	else
	{
		return snprintf(out, max_length, "%04d%02d%02d%02d%02d%02d", stm.tm_year+1900, stm.tm_mon+1, stm.tm_mday, stm.tm_hour, stm.tm_min, stm.tm_sec);
	}
}

INLINE
void DateTime::Set(int year, int month, int day, int hour, int minute, int second)
{
	time_t absolute_time = 0;

	if ((0 == year) && (0 == month) && (0 == day))
	{
		time(&absolute_time);
	}
	else
	{
		broken_time_.tm_year = year - 1900;
		broken_time_.tm_mon = month - 1;
		broken_time_.tm_mday = day;
		broken_time_.tm_hour = hour;
		broken_time_.tm_min = minute;
		broken_time_.tm_sec = second;

		broken_time_.tm_isdst = 0;
		absolute_time = mktime(&broken_time_);
	}

	// back to set broker_time_.tm_wday and broker_time_.tm_yday
	tm *bt = &broken_time_;
	TimeUtility::localtime_r(&absolute_time, bt);
}

}

#endif // __VS_TIME_UTIL_H__
